package com.zyw.orm.mybatis;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;

/**
 * 分页拦截器，处理rowBounds，规避掉mybatis的内存分页
 * 拦截mybatis statementhandler prepare 方法实现分页
 *
 * @author zhangyw
 * @date 2017/2/4 16:34
 * @see org.apache.ibatis.plugin.Interceptor
 * @see org.apache.ibatis.executor.statement.StatementHandler#prepare(Connection, Integer)
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PaginationInterceptor extends BaseInterceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MetaObject metaStatementHandler = getDelegateMetaObject(invocation.getTarget());
        RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");
        // 只重写需要分页的sql语句。默认重写以Page结尾的MappedStatement的sql
        if (RowBounds.NO_ROW_LIMIT != rowBounds.getLimit()) { // 当两者相等说明不用分页，反之分页
            BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
            String sql = boundSql.getSql();
            // 重写sql
            String pageSql = sql + " LIMIT " + rowBounds.getOffset() + "," + rowBounds.getLimit();
            metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
            // 采用物理分页后，就不需要mybatis的内存分页了，所以重置下面的两个参数
            metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
            metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);

//            if (logger.isDebugEnabled()) {
//                logger.debug("PageSql : " + pageSql);
//            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

}
